package controller;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.Random;

import modele.Square;
import modele.World;
import modele.WorldElem.Burning;
import modele.WorldElem.Earth;
import modele.WorldElem.Flooded;
import modele.WorldElem.Grass;
import modele.WorldElem.None;
import modele.WorldElem.Rock;
import modele.WorldElem.Sand;
import modele.WorldElem.SecondaryState;
import modele.WorldElem.Tree;
import modele.WorldElem.WaterSource;

public class GlobalRule extends Rule{

	
	
	private boolean[][] modified;
	
	
	public GlobalRule(World w) {
		super(w);
	}
	@Override
	public void applyRule(Earth e, Square s) {
		Square sWorld = getWorld().getSquareMap()[s.getPosX()][s.getPosY()];
		switch(sWorld.getSecondaryStateValue()){
			case SecondaryState.FLOODED :
				applyWaterFlow(s);
				break;
			case SecondaryState.NONE :
				if( !isModifyByWaterFlow(s.getPosX(), s.getPosY())){
					None n = (None) s.getSecondaryState();
					if(n.getNoneTime() > Earth.TOGRASS){
						Random r = new Random();
						if(r.nextFloat() < 0.04* (float)(n.getNoneTime()-Earth.TOGRASS)){
							Grass g = new Grass();
							s.setState(g);
						}
					}
					else 
					{
						n.setNoneTime(n.getNoneTime()+1);
					}
			}
		}
	}

	@Override
	public void applyRule(Grass g, Square s) {
		Square sWorld = getWorld().getSquareMap()[s.getPosX()][s.getPosY()];
		switch(sWorld.getSecondaryStateValue()){
			case SecondaryState.FLOODED :
				applyWaterFlow(s);
				break;
			case SecondaryState.BURNING :
				if(!isModifyByWaterFlow(s.getPosX(), s.getPosY())){
					if(((Burning)sWorld.getSecondaryState()).getBurningTime() >= 1){
						//l'herbe ne reste enflammé que 1 tour.
						s.setState(new Earth());
						s.setSecondaryState(new None());
					}
				}
				break;
			case SecondaryState.NONE :
				if(!isModifyByWaterFlow(s.getPosX(), s.getPosY())){
					ArrayList<ArrayList<Square>> squares = sWorld.getSquareAround(getWorld(), 1);
					Random r = new Random();
					int nbBurningState = getNbBurningState(squares);
					if (r.nextDouble() < 0.2 * (float)nbBurningState + 1.0/((double)30.0*(Math.pow(getWorld().getWidth(), 2)))){
						s.setSecondaryState(new Burning());
					}
					else{
						None n = (None) s.getSecondaryState();
						if(n.getNoneTime() > Grass.TOTREE){
							if(r.nextFloat() < Math.pow(getWorld().getWidth(), 3) * (double)(n.getNoneTime() - Grass.TOTREE)){
								Tree t = new Tree();
								s.setState(t);
							}
							else{
								((None) s.getSecondaryState()).setNoneTime(0);
							}
						}
						else{
							n.setNoneTime(n.getNoneTime()+1);
						}
					}
				}
				break;
		}
	}
	
	
	@Override
	public void applyRule(Rock r, Square s) {
		Square sWorld = getWorld().getSquareMap()[s.getPosX()][s.getPosY()];
		switch(sWorld.getSecondaryStateValue()){
			case SecondaryState.FLOODED :
				applyWaterFlow(s);
				break;
			case SecondaryState.NONE :
				if(!isModifyByWaterFlow(s.getPosX(), s.getPosY())){
					None n = (None) s.getSecondaryState();
					n.setNoneTime(n.getNoneTime()+1);
				}
				break;
		}
	}

	@Override
	public void applyRule(Sand sa, Square s) {
		Square sWorld = getWorld().getSquareMap()[s.getPosX()][s.getPosY()];
		switch(sWorld.getSecondaryStateValue()){
			case SecondaryState.FLOODED :
				applyWaterFlow(s);
				break;
		}
		
	}

	@Override
	public void applyRule(Tree t, Square s) {
		Square sWorld = getWorld().getSquareMap()[s.getPosX()][s.getPosY()];
		switch(sWorld.getSecondaryStateValue()){
			case SecondaryState.FLOODED :
				applyWaterFlow(s);
				break;
			case SecondaryState.BURNING :
				if(!isModifyByWaterFlow(s.getPosX(), s.getPosY())){
					((Burning)s.getSecondaryState()).setBurningTime(((Burning)sWorld.getSecondaryState()).getBurningTime()+1);
					//Un arbre met 5 tours à s'éteindre.
					if(((Burning)sWorld.getSecondaryState()).getBurningTime() >= 5){
						s.setState(new Earth());
						s.setSecondaryState(new None());
					}
				}
				break;
			case SecondaryState.NONE :
				if(!isModifyByWaterFlow(s.getPosX(), s.getPosY())){
					ArrayList<ArrayList<Square>> squares = sWorld.getSquareAround(getWorld(), 1);
					Random r = new Random();
					int nbBurningState = getNbBurningState(squares);
					if(nbBurningState > 0){
						//on enflamme l'herbe
						if(r.nextFloat() < 0.3 * (float)nbBurningState){
							s.setSecondaryState(new Burning());
						}
					}
					else if (r.nextDouble() < 1.0/((double)30.0*(Math.pow(getWorld().getWidth(), 2)))){
						s.setSecondaryState(new Burning());
					}
				}
				break;
		}
	}
	
	
	
	@Override
	public void applyRule(WaterSource ws, Square s) {
		applyWaterFlow(s);
		int waterProduction = 6;
		if(s.getSecondaryStateValue() == SecondaryState.FLOODED){
			Flooded f = (Flooded) s.getSecondaryState();
			f.setWaterLevel(f.getWaterLevel() + waterProduction);
		}
		else{
			Flooded f = new Flooded();
			f.setWaterLevel(waterProduction);
			s.setSecondaryState(f);
		}
		if(ws.getSecondaryStateValue() == SecondaryState.FLOODED ){
			Flooded f = (Flooded) ws.getSecondaryState();
			if(f.getTimeFlooded() > 50){
				Random rand = new Random();
				if(rand.nextFloat() < 0.15){
					Rock r = new Rock();
					r.setSecondaryState(f);
					s.setState(r);
					modified[s.getPosX()][s.getPosY()] = true;
				}
			}
		}
	}
	

	
	/**
	 * @param squares
	 * @param s
	 * @return
	 */
	private ArrayList<Square> getSquareOfSmallerAltitude(ArrayList<ArrayList<Square>> squares, Square s){
		Iterator<ArrayList<Square>> itArray = squares.iterator();
		ArrayList<Square> result = new ArrayList<Square>();
		while(itArray.hasNext()){
			Iterator<Square> itSquare = itArray.next().iterator();
			while(itSquare.hasNext()){
				Square tmp = itSquare.next();
				int deltaAltitude = s.getAltitude() - tmp.getAltitude(); 
				if(deltaAltitude > 1){
					result.add(tmp);
				}
			}
		}
		return result;
	}
	
	
	
	/**
	 * 
	 * @param s la case de la carte buffer que l'on visite
	 */
	private void applyWaterFlow(Square s){
		if(s.getSecondaryStateValue() == SecondaryState.FLOODED){
			if(s != getEvolvedSquareMap()[s.getPosX()][s.getPosY()]){
				throw new RuntimeException();
			}
			Square squareWorldBuff =(Square) getWorld().getSquareMap()[s.getPosX()][s.getPosY()].clone();
			ArrayList<ArrayList<Square>> squaresWorld = s.getSquareAround(getWorld(), 1);
			
			ArrayList<Square> smallerSquaresWorld = getSquareOfSmallerAltitude(squaresWorld, s);
			
			ArrayList<Square> bufferSquares = new ArrayList<Square>();
	 		
			for(Square square : smallerSquaresWorld){
				bufferSquares.add((Square) square.clone());
			}
			
			
			Random rand = new Random();
			int indice;
			while(!bufferSquares.isEmpty() && squareWorldBuff.getSecondaryStateValue() == SecondaryState.FLOODED){
				
				//on initialise l'indice
				//version aléatoire
	//			indice =  rand.nextInt(bufferSquares.size());
				
				//version vers le plus petit
				indice = getMinAltitudeIndex(bufferSquares);
				
				
				//on modifie le niveau de l'eau de squareWorldBuff qui nous assure que si
				//au début de l'application de l'event le niveau était de n on ne peut pas
				//répandre n+1 volume d'eau.
				Flooded fTmpBuf = (Flooded)(squareWorldBuff.getSecondaryState()); 
				fTmpBuf.setWaterLevel(fTmpBuf.getWaterLevel()-1);
				if(fTmpBuf.getWaterLevel() <= 0){
					None n = new None();
					squareWorldBuff.setSecondaryState(n);
				}
				
				//on modifie aussi le niveau de l'eau dans le tableau evolvedSquareMap
				//puisqu'il représente les modification qui seront réellement appliquées.
				Flooded fTmp = (Flooded)(s.getSecondaryState()); 
				fTmp.setWaterLevel(fTmp.getWaterLevel()-1);
				modified[s.getPosX()][s.getPosY()] = true;
				if(fTmp.getWaterLevel() <= 0){
					None n = new None();
					s.setSecondaryState(n);
				}
				
				
				//on modifie le tableau buffer pour s'assurer de la cohérence des boucle
				Square sTmpBuff = bufferSquares.get(indice);
				if(sTmpBuff.getSecondaryStateValue() != SecondaryState.FLOODED){
					fTmp = new Flooded();
					sTmpBuff.setSecondaryState(fTmp);
				}
				else{
					fTmp = (Flooded) sTmpBuff.getSecondaryState();
					fTmp.setWaterLevel(fTmp.getWaterLevel()+1);
				}
			
				
				
				//on met réellement à jour le tableau des evolved map
				Square sTmp = getEvolvedSquareMap()[sTmpBuff.getPosX()][sTmpBuff.getPosY()];
				modified[sTmp.getPosX()][sTmp.getPosY()] = true;
				if(sTmp.getSecondaryStateValue() != SecondaryState.FLOODED){
					fTmp = new Flooded();
					sTmp.setSecondaryState(fTmp);
				}
				else{
					fTmp = (Flooded) sTmp.getSecondaryState();
					fTmp.setWaterLevel(fTmp.getWaterLevel()+1);
				}
				
				
				//on retire de la liste des bufferTmp l'ensemble 
				Iterator<Square> it = bufferSquares.iterator();
				while(it.hasNext()){
					sTmp = it.next();
					if(squareWorldBuff.getAltitude() - sTmp.getAltitude() <= 1){
						it.remove();
					}
				}
			}
			
			//si la case était inondé à n est l'est à n+1 alors on augmente timeFlooded
			if(getWorld().getSquareMap()[s.getPosX()][s.getPosY()].getSecondaryStateValue() == SecondaryState.FLOODED
					&& s.getSecondaryStateValue() == SecondaryState.FLOODED){
				Flooded f = ((Flooded)(s.getSecondaryState()));
				f.setTimeFlooded(f.getTimeFlooded()+1);
				modified[s.getPosX()][s.getPosY()] = true;
			}
		}
	}
	
	public boolean isModifyByWaterFlow(int i, int j){
		return modified[i][j];
	}
	
	
	@Override
	public void prepareToEvolve() {
		setEvolvedSquareMap(getWorld().cloneSquareMap());
		modified = new boolean[getWorld().getWidth()][getWorld().getWidth()];
		
	}

	@Override
	public int getRuleValue() {
		return GLOBALRULE;
	}

	/**
	 * retourne l'index de la plus petite altitude.
	 *
	 * @param squares ne doit pas être vide
	 * @return
	 */
	public int getMinAltitudeIndex(ArrayList<Square> squares){
		int minIndex = 0;
		int minAltitude = squares.get(minIndex).getAltitude();
		for(int i = 1 ; i < squares.size() ; i++){
			int altitudeTmp =squares.get(i).getAltitude(); 
			if(altitudeTmp < minAltitude){
				minAltitude = altitudeTmp;
				minIndex = i;
			}
		}
		return minIndex;
	}
	
	

	private int getNbBurningState(ArrayList<ArrayList<Square>> squares){
		//On compte le nombre de case brulant à coté.
		Iterator<ArrayList<Square>> itArray = squares.iterator();
		int nbBurningState = 0;
		while(itArray.hasNext()){
			Iterator<Square> itSquare = itArray.next().iterator();
			while(itSquare.hasNext()){
				Square tmp = itSquare.next();
				if(tmp.getSecondaryStateValue() == SecondaryState.BURNING){
					nbBurningState++;
				}
			}
		}
		return nbBurningState;
	}

}
